我們在進階實作-關於第三方登入(一)的文章中,已經講述完關於oauth2及Facebook第三方登入的運行方式。接續,我們試著用Node.js來實作這部分的功能。
在實作前,我們先去Facebook的開發人員頁面去申請個APP 來使用,因為若沒該申請APP我們也沒辦法使用它的服務。
首先,先點選該頁面右上角的我的應用程式
,並選擇當中的新增應用程式
選項。
之後,建立一個你想取的APP名稱,並按建立應用程式編號
按鈕。
接續選取Facebook登入
的功能。
再來,進入到主控版,記取通用程式編號
及通用程式密鑰
,這兩個東西待我們在撰寫第三方登入
時會使用到。
在Node.js中,有套件可以協助我們來開發Facebook的第三方登入。
Strategy
。而passport-facebook
就是passport眾多Strategy
中的一種,專門用來提供連接facebook的套件。passport-facebook
套件的做法是會將由Facebook那邊取回來的token及用戶資料存在session中,所以我們需要再額外使用這個套件來記取token。$ npm install passport
$ npm install passport-facebook
$ npm install express-session
這部分我們依舊透過Express應用程式產生器來開啟一個專案,並將結構分成:
.
├── app.js
├── bin
│ └── www
├── config
│ └── passport.js
├── package.json
├── public
│ ├── images
│ ├── javascripts
│ └── stylesheets
│ └── style.css
├── routes
│ ├── login.js
│ └── users.js
├── views
├── error.ejs
├── index.ejs
└── success.ejs
├── .env
└── .gitignore
這部分,筆者會根據進階實作-關於第三方登入(一)文章中提到的Facebook Oauth2的運行流程,來跟passport-facebook所提供的範例程式碼做搭配說明。
+--------+ +---------------+
| |--(A)- Authorization Request ->| Resource |
| | | Owner |
| |<-(B)-- Authorization Grant ---| |
| | +---------------+
| |
| | +---------------+
| |--(C)-- Authorization Grant -->| Authorization |
| Client | | Server |
| |<-(D)----- Access Token -------| |
| | +---------------+
| |
| | +---------------+
| |--(E)----- Access Token ------>| Resource |
| | | Server |
| |<-(F)--- Protected Resource ---| |
+--------+ +---------------+
Figure: Facebook Protocol Flow
在passport-facebook
套件中,這部分的處理會是放在:
app.get('/auth/facebook',
passport.authenticate('facebook'));
也就是前端的開發夥伴可以透過/auth/facebook
的API,來連接到Facebook那邊來顯示像下述圖的狀況。讓Facebook的使用者來做個同意授權的動作。
figure from: https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow/
待使用者同意授權後,就會取得(B)-- Authorization Grant
步驟的認證資料。
待server端這邊拿到認證資料後,會再透過下列的function來做處理:
passport.use(new FacebookStrategy({
clientID: FACEBOOK_APP_ID,
clientSecret: FACEBOOK_APP_SECRET,
callbackURL: "http://localhost:3000/auth/facebook/callback"
},
function(accessToken, refreshToken, profile, cb) {
User.findOrCreate({ facebookId: profile.id }, function (err, user) {
return cb(err, user);
});
}
));
可以由上述的程式碼看到passport
使用了Facebook的Strategy
,且在申請Facebook APP
階段,筆者有提到要讀者記取的通用程式編號
及通用程式密鑰
會在這部分用到。
clientID
中放入通用程式編號。clientSecret
中放入通用程式密鑰。而callbackURL
指的是在取得用戶同意授權後,會在導向到該URL中。
註記:這部分的步驟等同於上篇文章中所提到的由Facebook所提供的API來呼叫的
C: Client端要向authorization server來索取access token。
部分是一樣的。
但方便的是在這步驟它一起幫我們解決了(D)
, (E)
及(F)--- Protected Resource
的動作。該步驟的處理不僅僅只有取得accessToken
,也會將需要透過token才能取得的受保護的敏感性資料(Facebook用戶資料)一併匯入到該function的cb(callback)上。但該套件會將cb的資料放置session中,所以也額外提供了:
passport.serializeUser(function(user, cb) {
cb(null, user);
});
passport.deserializeUser(function(obj, cb) {
cb(null, obj);
});
這部分的意思是它將用戶資料給serialize(序列化)
並再透過deserialize(反序列化)
的方式來將用戶資料放置session中。
最後,在上面有提到的callbackURL
所做的重導向(redirect)動作,則是在這function中做處理:
app.get('/auth/facebook/callback',
passport.authenticate('facebook', { failureRedirect: '/login' }),
function(req, res) {
// Successful authentication, redirect home.
res.redirect('/');
});
也就是若用戶同意授權則會轉到/
的這個URL中,若不同意則會轉到/login
的URL中。
我們這部分會用後端render
的方式來進行頁面開發。因為這部分若要測試只能實際寫個頁面來測試,Postman並沒有幫法協助這部分的測試。等同於說頁面的使用流程會是:
+------------+ +-------------+
| index.html | ------ 成功登入 ------> | success.html |
+------------+ +-------------+
匯入到routes
資料夾的login.js
檔案:
var express = require('express');
var router = express.Router();
const passport = require('../config/passport');
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
router.get('/success', function(req, res, next) {
// console.log(req.user);
res.render('success', {data: req.user});
})
router.get('/auth/facebook', passport.authenticate('facebook'));
router.get('/auth/facebook/callback',
passport.authenticate('facebook', { successRedirect: '/success',
failureRedirect: '/' }));
module.exports = router;
到config
資料夾的passport.js
檔案中寫入:
var passport = require('passport')
, FacebookStrategy = require('passport-facebook').Strategy;
passport.use(new FacebookStrategy({
clientID: FACEBOOK_APP_ID,
clientSecret: FACEBOOK_APP_SECRET,
callbackURL: "http://localhost:3000/auth/facebook/callback"
},
function (accessToken, refreshToken, profile, done) {
return done(null, profile);
}
));
passport.serializeUser(function (user, cb) {
cb(null, user);
});
passport.deserializeUser(function (obj, cb) {
cb(null, obj);
});
module.exports = passport;
註記:clientID及clientSecret請寫入讀者自行申請到的編號及密鑰。
session的部份我們需要到app.js
中做設定,才會讓我們的專案有session可運行。
var passport = require('passport');
// =========
// ...
app.use(express.static(path.join(__dirname, 'public')));
// =========
// 請自行在該行底下新增下列三行code
// passport-middleware
app.use(require('express-session')({ secret: 'keyboard cat', resave: true, saveUninitialized: true }));
app.use(passport.initialize());
app.use(passport.session());
為了測試,我們就寫個簡陋的前端Code...(真的非常簡陋)。到views
資料夾的index.ejs
檔案中寫入:
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
<h1><%= title %></h1>
<p>Welcome to <%= title %></p>
<a href="/auth/facebook">Log In with Facebook</a>
</body>
</html>
接續在success.ejs
檔案中寫入:
<!DOCTYPE html>
<html>
<head>
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
<h1>hi, <%=data.displayName%></h1>
</body>
</html>
首先,我們先用npm start
的方式開啟專案,之後開啟瀏覽器並輸入localhost:3000
會看到:
點選剛剛所寫好的超連結部分Log In with Facebook
後會看到:
這代表我們在Facebook APP那邊的設定還沒有完全。先到Facebook 登入
點選設定
按鈕,並在右邊的有效的OAuth重新導向URL
中輸入http://localhost:3000/auth/facebook/callback
。
之後,再回到一開始的localhost:3000
頁面,並點選超連結部分後就會看到:
按下確認鈕後。
就成功看到我們用戶自己的displayname
了!
順帶一提,筆者發現Facebook的政策有改變,如果想要將APP對外發佈的話目前需要額外再輸入有該APP的「隱私政策網址」。不像之前想發佈就可以直接發佈,筆者在猜可能是為了用戶的安全考量才會有這政策。
但有趣的是它驗證是否是有效的隱私政策網址
的方式是看domain連過去的實體IP有沒有效。變成說就算筆者輸入https://google.com
也會通過(笑),不過假設真的要發佈這個APP,筆者相信應該大家都還是會用自己網站的網址才是。
各家社群網站的第三方登入服務其規範或多或少會有些許不同,像Facebook本身其實沒有提供refresh token
,但像Google就有提供。所以,讀者若想要嘗試實作其它社群網站的第三方登入功能,不仿先從該社群網站所提供的API文件看起。
註記:關於refresh token可參考RFC-6749-1.5. Refresh Token。
繼說明原理並接續基本實作後,不知道讀者對於第三方登入有沒有更進一步的理解?接下來,我們將前進金流
的進階實作部分。